
#ifndef __LIBERTY_LIBRARY_ASSETDEPOTFOLDER_H__
#define __LIBERTY_LIBRARY_ASSETDEPOTFOLDER_H__

#include <Liberty3D/Engine/AssetFileFolder.h>
#include <Liberty3D/Engine/ISerializable.h>
#include <Liberty3D/Engine/ISerializableFactory.h>
#include <Liberty3D/Engine/IMemoryListener.h>

#include <Liberty3D/Liberty3D.h>

#include <Liberty3D/Engine/Uuid.h>

#include <Liberty3D/Library/Mutex/Abstract.h>
#include <Liberty3D/Library/Mutex/Boost.h>

#include <Liberty3D/Library/IAssetDepot.h>
#include <Liberty3D/Library/IMutex.h>
#include <Liberty3D/Library/MappedAsset.h>
#include <Liberty3D/Library/SyncedProcessQueue.h>
#include <Liberty3D/Library/SyncedCache.h>

#include <functional>
#include <unordered_map>
#include <list>
#include <string>

namespace Liberty { namespace Library {
    class AssetDepotEntryFolder;

    class AssetDepotEntry : public Liberty::Engine::ISerializable {
    public:
        Liberty::Engine::Uuid uuid;
        std::string           name;
        unsigned int          hashMeta;
        size_t                revisionMeta;

        std::weak_ptr<AssetDepotEntryFolder> parent;

    public:
        AssetDepotEntry();
        virtual ~AssetDepotEntry();
        
        std::string projectPath() const;

    public:
        virtual void serializeObject(Liberty::Engine::ISerializer*);
        virtual void serializeEnd(Liberty::Engine::ISerializer*);
    };

    class AssetDepotEntryFile : public AssetDepotEntry {
    public:
        unsigned int hash;
        size_t       revision;

    public:
        AssetDepotEntryFile();
        
    public:
        virtual void serializeObject(Liberty::Engine::ISerializer*);
        virtual const char* serializeClassName() const;
    };

    class AssetDepotEntryFolder : public AssetDepotEntry {
    public:
        typedef std::vector<std::shared_ptr<AssetDepotEntry>> EntryList;
        
        EntryList childs;
        
    public:
        AssetDepotEntryFolder();
        
    public:
        std::shared_ptr<AssetDepotEntry> find(const std::string&) const;
        
        void add   (const std::shared_ptr<AssetDepotEntry>&);
        void remove(const std::shared_ptr<AssetDepotEntry>&);
        
        std::list<std::string> names() const;
        
    public:
        virtual void serializeObject(Liberty::Engine::ISerializer*);
        virtual const char* serializeClassName() const;
    };

    class AssetDepotFolderDetectState {
    public:
        typedef std::list<std::shared_ptr<AssetDepotEntry>>                                   AssetDepotEntryList;
        typedef std::list<std::shared_ptr<AssetDepotEntryFolder>>                             AssetDepotEntryFolderList;
        typedef std::list<std::pair<std::shared_ptr<AssetDepotEntry>, std::string>>           AssetDepotEntryMovedList;
        typedef std::list<std::pair<std::shared_ptr<AssetDepotEntry>, Liberty::Engine::Uuid>> AssetDepotEntryDuplicateList;

        AssetDepotEntryFolderList    pendings;
        AssetDepotEntryList          unfound;
        AssetDepotEntryList          added;
        AssetDepotEntryList          modified;
        AssetDepotEntryMovedList     moved;
        AssetDepotEntryDuplicateList uuids;

        std::unique_ptr<ISharedLock>           lock;
        std::shared_ptr<AssetDepotEntryFolder> folder;
        std::string                            name;
        std::string                            shortName;

        std::list<Liberty::Engine::Uuid> metaFileMissing;

    public:
        AssetDepotFolderDetectState();
    };

    class AssetDepotFolder : public Liberty::Engine::AssetFileFolder, public IAssetDepot, public Liberty::Engine::IMemoryListener, public std::enable_shared_from_this<AssetDepotFolder> {
        friend class IAssetDepot;

    protected:
        class Processor {
        public:
            void operator()(const std::shared_ptr<MappedAsset>& asset, std::unique_ptr<ILock>& lock);
        };

        class Queuer {
        public:
            AssetDepotFolder* folder;

        public:
            Queuer(AssetDepotFolder* f);

        public:
            void operator()();
        };

        class ImporterCache : public SyncedCacheBasicConfig<Liberty::Engine::Uuid, AssetImporter> {
        public:
            AssetDepotFolder* folder;

        public:
            ImporterCache(AssetDepotFolder* f);

        public:
            std::shared_ptr<AssetImporter> create(const Liberty::Engine::Uuid& uuid, bool& created);
            void dirty(const std::shared_ptr<AssetImporter>& importer);
        };

        class ImporterFactory : public Liberty::Engine::ISerializableFactory {
        public:
            AssetDepotFolder* folder;

        public:
            ImporterFactory(AssetDepotFolder* f);

        public:
            virtual std::shared_ptr<Liberty::Engine::ISerializable> create(const char* name);
        };

    protected:
        typedef std::vector<std::shared_ptr<IAssetDepotListener>>                           AssetDepotListenerVector;
        typedef std::unordered_map<Liberty::Engine::Uuid, std::shared_ptr<AssetDepotEntry>> AssetDepotEntryMap;
        typedef SyncedCache<ImporterCache>                                                  AssetDepotImporterCache;
        typedef std::list<std::shared_ptr<AssetDepotEntry>>                                 AssetDepotEntryList;
        typedef std::list<std::shared_ptr<AssetDepotEntryFolder>>                           AssetDepotEntryFolderList;

        typedef SyncedProcessQueue<std::shared_ptr<MappedAsset>, Processor, Queuer, Mutex::Boost, ILock> AssetDepotDirtyList;

        /** Protect access to _map, _root and any AssetDepotEntry objects owned by this instance. */
        std::shared_ptr<ISharedMutex>          _entryMutex;
        AssetDepotEntryMap                     _map;
        std::shared_ptr<AssetDepotEntryFolder> _root;
        size_t                                 _revision;
        bool                                   _libraryDirty;
        std::shared_ptr<Task>                  _libraryDirtyTask;
        volatile bool                          _detectChangesRunning;

        AssetDepotImporterCache              _importerCache;
        AssetDepotListenerVector             _listeners;
        AssetDepotDirtyList                  _dirty;

    protected:
        AssetDepotFolder(const std::string& folder);

        void init(bool create);

    public:
        virtual ~AssetDepotFolder();

    public: // IAssetFile
        virtual bool exists(const std::string& resourceName) const;
        virtual std::streambuf* open(const std::string& resourceName, std::ios::openmode mode) const;
        virtual void list(const std::string& folder, std::vector<std::string>& entries) const;

    public: // IMutableAssetFile
        virtual void makeDirectory(const std::string& folder, bool createIntermediates = false);
        virtual void remove(const std::string& path, bool recursive = false);
        virtual void sync();

    public: // IAssetDepot
        virtual void addListener(const std::shared_ptr<IAssetDepotListener>&);
        virtual void removeListener(const std::shared_ptr<IAssetDepotListener>&);
        virtual void detectChanges();

        virtual std::string           pathForUuid(const Liberty::Engine::Uuid& uuid) const;
        virtual Liberty::Engine::Uuid uuidForPath(const std::string& fileName) const;

        virtual std::shared_ptr<AssetImporter>   importer  (const Liberty::Engine::Uuid&);
        virtual std::shared_ptr<AssetCachedData> cachedData(const Liberty::Engine::Uuid&);
        virtual std::shared_ptr<AssetBuildData>  buildData (const Liberty::Engine::Uuid&, const std::string& buildTag);
        virtual std::list<std::string>           buildDatas(const Liberty::Engine::Uuid&);
        virtual void                             reimport  (const Liberty::Engine::Uuid&);

    public: // IMemoryListener
        virtual void onMemoryLow();
        virtual void onMemoryGarbageCollect();

    public:
        size_t revision() const { return _revision; }
        void dirty(const std::shared_ptr<AssetImporter>&);
        void dirty(const std::shared_ptr<AssetCachedData>&);
        void dirty(const std::shared_ptr<AssetBuildData>&);

        void updateFile(const std::shared_ptr<AssetDepotEntryFile>& entry);
        void updateMeta(const std::shared_ptr<AssetDepotEntry>& entry);

    protected:
        void dirtyLibraryLocked();
        void saveDirtyToDisk();
        void processDirtyAssets();

        void detectChanges(AssetDepotFolderDetectState& state);
        void detectChanges(AssetDepotFolderDetectState& state, bool folder, unsigned int hash, unsigned int hashMeta);

        void recursiveRemove(AssetDepotEntryList& unfound, const std::shared_ptr<AssetDepotEntryFolder>&);

        virtual bool shouldIgnoreFile(const char* name, bool excludeMeta = true) const;
        virtual bool shouldIgnorePath(const std::string& name) const;
        virtual bool shouldCreateMetaFile(const std::string& name) const;

        virtual Liberty::Engine::Uuid createUuidForPath(const std::string& name) const;
        virtual void updateUuidForPath(const std::shared_ptr<AssetDepotEntry>& entry);

        void createEntry(AssetDepotFolderDetectState& state, const std::shared_ptr<AssetDepotEntry>& entry);

        std::shared_ptr<AssetDepotEntryFolder> changeFileToFolder(AssetDepotFolderDetectState& state, const std::shared_ptr<AssetDepotEntryFile>&);
        std::shared_ptr<AssetDepotEntryFolder> createFolder      (AssetDepotFolderDetectState& state);

        std::shared_ptr<AssetDepotEntryFile> changeFolderToFile(AssetDepotFolderDetectState& state, const std::shared_ptr<AssetDepotEntryFolder>&);
        std::shared_ptr<AssetDepotEntryFile> createFile        (AssetDepotFolderDetectState& state);

        void metadataChanged(AssetDepotFolderDetectState& state, const std::shared_ptr<AssetDepotEntry>& entry, unsigned int hashMeta);
        void duplicateId    (AssetDepotFolderDetectState* state, const std::shared_ptr<AssetDepotEntry>& entry);

        void buildMap();
        void buildMap(const std::shared_ptr<AssetDepotEntryFolder>&);

        unsigned int hashFile(const std::string& fileName);

        std::shared_ptr<AssetCachedData> createCachedData(const Liberty::Engine::Uuid& uuid, bool& created);
        std::shared_ptr<AssetBuildData>  createBuildData (const Liberty::Engine::Uuid& uuid, const std::string& buildTag, bool& created);

        std::string importerPath  (const Liberty::Engine::Uuid& uuid, std::string& path) const;
        std::string cachedDataPath(const Liberty::Engine::Uuid& uuid) const;
        std::string buildDataPath (const Liberty::Engine::Uuid& uuid) const;
        std::string buildDataPath (const Liberty::Engine::Uuid& uuid, const std::string& buildTag) const;

        void generateMetaFiles(const std::list<Liberty::Engine::Uuid>&);

    public:
        virtual void serializeObject(Liberty::Engine::ISerializer* serializer);
        virtual void serializeEnd   (Liberty::Engine::ISerializer* serializer);

        Liberty::Engine::ISerializer* serializerForImporter(const Liberty::Engine::Uuid& uuid, bool read);

    protected:
        void load();

    public:
        virtual void save();

        void saveLocked(const std::shared_ptr<MappedAsset>& asset);
        void saveLocked(const std::shared_ptr<AssetImporter>& asset);
    };
}}

#endif
